/**
@author		Jong Min, Lee
@date		  10/01/2014
@brief		StudySetPool class 

copyright(c) Tae Young Soft. All right reserved. 2014
*/
#include "stdafx.h"
#include "Taurus_Viewer/StudySetPool.h"
#include <algorithm>
#include "Taurus_Util/Errors.h"
#include "Taurus_DcmData/IStudySetInfo.h"
#include "Taurus_DcmData/IStudyInfo.h"
#include "Taurus_DcmData/ISeriesInfo.h"
#include "Taurus_DcmData/IInstanceInfo.h"
#include "Taurus_DcmData/Study.h"
#include "Taurus_DcmData/Series.h"
#include "Taurus_DcmData/Instance.h"
#include "Taurus_DcmData/DcmStudy.h"
#include "Taurus_DcmData/DcmSeries.h"
#include "Taurus_DcmData/DcmInstance.h"
#include "Taurus_DcmData/DcmFrame.h"

CStudySetPool* CStudySetPool::m_theInstance = NULL;

CStudySetPool::CStudySetPool()
{
  init();
}

CStudySetPool::~CStudySetPool()
{
  destroy();
}

CStudySetPool* CStudySetPool::getInstance()
{
  if(m_theInstance == NULL)
    _TRY_ALLOC(m_theInstance, CStudySetPool);
  
  return m_theInstance;
}

DWORD CStudySetPool::createAndAddStudySet(IStudySetInfo& studySetInfo, 
                                          DcmStudyMedia media, 
                                          StudySetOpenMode openMode, 
                                          CStudySet*& outStudySet)
{
  CStudySet* newStudySet = NULL;
  DWORD error = createStudySet(studySetInfo, media, openMode, newStudySet);
  if(error != ERROR_SUCCESS) return error;

  error = addStudySet(newStudySet);
  if(error != ERROR_SUCCESS)
  {
    destroyStudySet(newStudySet);
    return error;
  }

  outStudySet = newStudySet;
  return ERROR_SUCCESS;
}

DWORD CStudySetPool::removeStudySet(CStudySet* studySet)
{
  DWORD error = ERROR_SUCCESS;
  for(auto pos = m_theStudySets.begin(), end = m_theStudySets.end();
      pos != end;
      ++pos)
  {
    CStudySet* sso = (*pos);
    if(sso == studySet)
    {
      error = destroyStudySet(sso);
      if(error == ERROR_SUCCESS)
        m_theStudySets.erase(pos);
      return error;
    }
  }

  return ERROR_STUDY_SET_NOT_FOUND;
}

void CStudySetPool::removeAllStudySet()
{
  for(auto pos = m_theStudySets.begin(), end = m_theStudySets.end();
      pos != end;
      ++pos)
  {
    CStudySet* studySet = (*pos);
    destroyStudySet(studySet);
  }
  m_theStudySets.clear();
}

void CStudySetPool::init()
{
}

void CStudySetPool::destroy()
{
  removeAllStudySet();
}

DWORD CStudySetPool::createStudySet(IStudySetInfo& studySetInfo,
                                    DcmStudyMedia media,
                                    StudySetOpenMode openMode,
                                    CStudySet*& outStudySet)
{
  DWORD error = ERROR_SUCCESS;
  std::vector<CStudy*> newStudies;

  int numStudyInfo = studySetInfo.getNumStudyInfo();
  if(numStudyInfo <= 0) ERROR_INVALID_STUDY_INFO;

  for(int studyInfoIdx = 0; studyInfoIdx < numStudyInfo; ++studyInfoIdx)
  {
    IStudyInfo* studyInfo = studySetInfo.getStudyInfo(studyInfoIdx);
    if(studyInfo == NULL) continue;

    CString studyKey = studyInfo->getKey();
    if(studyKey.IsEmpty()) continue;

    CStudy* newStudy = NULL;
    CDcmStudy* existenDcmStudy = findDcmStudy(studyKey);
    if(existenDcmStudy == NULL)
      error = createStudy(*studyInfo, media, NULL, newStudy);
    else
      error = createStudy(*studyInfo, existenDcmStudy, NULL, newStudy);

    if(error != ERROR_SUCCESS)
    {
      if(error == ERROR_INVALID_STUDY_INFO)
      {
        error = ERROR_SUCCESS;
        continue;
      }

      break;
    }

    try
    {
      newStudies.push_back(newStudy);
    }
    catch(...)
    {
      CString strKey = newStudy->getKey();
      _DELETE(newStudy);
      findAndRemoveDcmStudy(strKey);
      error = ERROR_NOT_ENOUGH_MEMORY;
      break;
    }
  }

  if(error == ERROR_SUCCESS && newStudies.size() == 0)
    return ERROR_INVALID_STUDY_INFO;

  if(error == ERROR_SUCCESS)
  {
    CStudySet* newStudySet = NULL;
    error = CStudySetProducer::produce(newStudies.data(),
                                       newStudies.size(),
                                       0,
                                       openMode,
                                       newStudySet);
    if(error == ERROR_SUCCESS)
    {
      outStudySet = newStudySet;
      newStudies.clear();
    }
  }

  if(error != ERROR_SUCCESS)
  {
    const unsigned int numNewStudeis = newStudies.size();
    for(unsigned int i = 0; i < numNewStudeis; ++i)
    {
      const TCHAR* key = newStudies[i]->getKey();
      _DELETE(newStudies[i]);
      findAndRemoveDcmStudy(key);
    }
    newStudies.clear();
  }

  return error;
}

DWORD CStudySetPool::createStudy(IStudyInfo& studyInfo, 
                                 DcmStudyMedia media, 
                                 CStudySet* parent, 
                                 CStudy*& outStudy)
{
  CDcmStudy* newDcmStudy = NULL;
  DWORD error = CDcmStudyProducer::produce(studyInfo, media, newDcmStudy);
  if(error != ERROR_SUCCESS) return error;

  CStudy* newStudy = NULL;
  error = CStudyProducer::produce(newDcmStudy, parent, outStudy);
  if(error != ERROR_SUCCESS)
  {
    _DELETE(newDcmStudy);
    return error;
  }

  error = addDcmStudy(newDcmStudy);
  if(error != ERROR_SUCCESS)
  {
    _DELETE(newStudy);
    _DELETE(newDcmStudy);
    return error;
  }

  outStudy = newStudy;
  return error;
}

DWORD CStudySetPool::createStudy(IStudyInfo& studyInfo, 
                                 CDcmStudy* existentDcmStudy, 
                                 CStudySet* parent, 
                                 CStudy*& outStudy)
{
  if(existentDcmStudy == NULL) return ERROR_INVALID_PARAMETER;

  unsigned numImageSeries = 0;
  std::vector<CSeries*> newSeriesArr;

  DWORD error = ERROR_SUCCESS;
  int numSeriesInfo = studyInfo.getNumSeriesInfo();
  for(int i = 0; i < numSeriesInfo; ++i)
  {
    ISeriesInfo* seriesInfo = studyInfo.getSeriesInfo(i);
    if(seriesInfo == NULL) continue;

    CString seriesKey = seriesInfo->getKey();
    if(seriesKey.IsEmpty()) continue;

    CSeries* newSeries = NULL;
    CDcmSeries* existenDcmSeries = existentDcmStudy->findSeries(seriesKey);
    if(existenDcmSeries == NULL)
      error = createSeries(*seriesInfo, NULL, existentDcmStudy, newSeries);
    else
      error = createSeries(*seriesInfo, existenDcmSeries, NULL, newSeries);

    if(error != ERROR_SUCCESS)
    {
      if(error == ERROR_INVALID_SERIES_INFO)
      {
        error = ERROR_SUCCESS;
        continue;
      }
      break;
    }

    try
    {
      newSeriesArr.push_back(newSeries);
    }
    catch(...)
    {
      _DELETE(newSeries);
      existentDcmStudy->removeNoRefSeries();
      error = ERROR_NOT_ENOUGH_MEMORY;
      break;
    }

    if(!newSeries->isNoneImage()) numImageSeries++;
  }

  if(error == ERROR_SUCCESS && (newSeriesArr.size() == 0))
    return ERROR_INVALID_STUDY_INFO;

  if(error == ERROR_SUCCESS && numImageSeries == 0)
    error = ERROR_INVALID_STUDY_INFO;

  if(error == ERROR_SUCCESS)
  {
    CStudy* newStudy = NULL;
    error = CStudyProducer::produce(existentDcmStudy,
                                    newSeriesArr.data(),
                                    newSeriesArr.size(),
                                    parent,
                                    newStudy);
    if(error == ERROR_SUCCESS)
    {
      outStudy = newStudy;
      newSeriesArr.clear();
    }
  }

  if(error != ERROR_SUCCESS)
  {
    for(unsigned int i = 0; i < newSeriesArr.size(); ++i)
    {
      _DELETE(newSeriesArr[i]);
    }
    newSeriesArr.clear();
    existentDcmStudy->removeNoRefSeries();
  }

  return error;
}

DWORD CStudySetPool::createSeries(ISeriesInfo& seriesInfo, 
                                  CStudy* parentStudy, 
                                  CDcmStudy* parentDcmStudy, 
                                  CSeries*& outSeries)
{
  CDcmSeries* newDcmSeries = NULL;
  DWORD error = CDcmSeriesProducer::produce(seriesInfo,
                                            parentDcmStudy,
                                            newDcmSeries);
  if(error != ERROR_SUCCESS) return error;

  CSeries* newSeries = NULL;
  error = CSeriesProducer::produce(newDcmSeries,
                                   parentStudy,
                                   newSeries);
  if(error != ERROR_SUCCESS)
  {
    _DELETE(newDcmSeries);
    return error;
  }

  error = parentDcmStudy->addSeries(newDcmSeries);
  if(error != ERROR_SUCCESS)
  {
    _DELETE(newSeries);
    _DELETE(newDcmSeries);
    return error;
  }

  outSeries = newSeries;
  return ERROR_SUCCESS;
}

DWORD CStudySetPool::createSeries(ISeriesInfo& seriesInfo, 
                                  CDcmSeries* existentDcmSeries, 
                                  CStudy* parent, 
                                  CSeries*& outSeries)
{
  if(existentDcmSeries == NULL) return ERROR_INVALID_PARAMETER;

  DWORD ec = ERROR_SUCCESS;
  std::vector<CInstance*> newInstances;

  int numInstanceInfo = seriesInfo.getNumInstanceInfo();
  unsigned int numExistentDcmInstances = existentDcmSeries->getNumInstances();
  if(numInstanceInfo == static_cast<unsigned int>(numExistentDcmInstances))
  {
    CSeries* newSeries = NULL;
    ec = CSeriesProducer::produce(existentDcmSeries,
                                  parent,
                                  newSeries);
    if(ec == ERROR_SUCCESS)
      outSeries = newSeries;

    return ec;
  }
  else
  {
    for(int i = 0; i < numInstanceInfo; ++i)
    {
      IInstanceInfo* instanceInfo = seriesInfo.getInstanceInfo(i);
      if(instanceInfo == NULL) continue;

      CString strInstanceKey = instanceInfo->getKey();
      if(strInstanceKey.IsEmpty()) continue;
      CDcmInstance* existentDcmInstance = existentDcmSeries->findInstance(strInstanceKey);
      if(existentDcmInstance == NULL)
      {
        ec = CDcmInstanceProducer::produce(*instanceInfo,
                                           existentDcmSeries,
                                           existentDcmInstance);
        if(ec != ERROR_SUCCESS) break;

        ec = existentDcmSeries->addInstance(existentDcmInstance);
        if(ec != ERROR_SUCCESS)
        {
          _DELETE(existentDcmInstance);
          break;;
        }
      }

      CInstance* newInstance = NULL;
      ec = CInstanceProducer::produce(existentDcmInstance,
                                      NULL,
                                      newInstance);
      if(ec != ERROR_SUCCESS) break;

      try
      {
        newInstances.push_back(newInstance);
      }
      catch(...)
      {
        _DELETE(newInstance);
        ec = ERROR_NOT_ENOUGH_MEMORY;
        break;
      }
    }

    if(ec = ERROR_SUCCESS && newInstances.size() == 0)
      return ERROR_NOT_ENOUGH_MEMORY;

    if(ec == ERROR_SUCCESS)
    {
      ec = CSeriesProducer::produce(existentDcmSeries,
                                    newInstances.data(),
                                    newInstances.size(),
                                    parent,
                                    outSeries);
      if(ec == ERROR_SUCCESS) newInstances.clear();
    }

    if(ec != ERROR_SUCCESS)
    {
      for(unsigned int i = 0; i < newInstances.size(); ++i)
        _DELETE(newInstances[i]);
      newInstances.clear();
      existentDcmSeries->removeNotRefInstances();
    }
  }//else

  return ec;
}

DWORD CStudySetPool::addStudySet(CStudySet* studySet)
{
  if(studySet == NULL) return ERROR_INVALID_PARAMETER;

  try
  {
    m_theStudySets.push_back(studySet);
  }
  catch(...)
  {
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  return ERROR_SUCCESS;
}

DWORD CStudySetPool::destroyStudySet(CStudySet* studySet)
{
  if(studySet == NULL) return ERROR_INVALID_PARAMETER;

  std::vector<CString> studyKeys;
  unsigned int numStudies = studySet->getNumStudies();
  try
  {
    studyKeys.reserve(numStudies);
  }
  catch(...)
  {
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  for(unsigned int i = 0; i < numStudies; ++i)
  {
    try
    {
      studyKeys.push_back(studySet->getStudy(i)->getKey());
    }
    catch(CMemoryException* e)
    {
      e->Delete();
      studyKeys.clear();
      return ERROR_NOT_ENOUGH_MEMORY;
    }
  }

  unsigned int numKeys = studyKeys.size();
  for(unsigned int i = 0; i < numKeys; ++i)
  {
    CString strKey;
    try
    {
      strKey = studyKeys[i];
    }
    catch(CMemoryException* e)
    {
      e->Delete();
      return ERROR_NOT_ENOUGH_MEMORY;
    }

    findAndRemoveDcmStudy(strKey);
  }

  return ERROR_SUCCESS;
}

DWORD CStudySetPool::findAndRemoveDcmStudy(const TCHAR* key)
{
  if(key == NULL) return ERROR_INVALID_PARAMETER;
  auto pos = find_if(m_theDcmStudies.begin(), m_theDcmStudies.end(),
    [&key](CDcmStudy* dcmStudy)
  {
    return _tcscmp(key, dcmStudy->getKey()) == 0;
  });

  if(pos == m_theDcmStudies.end())
    return ERROR_STUDY_NOT_FOUND;

  if((*pos)->getRefCount() <= 0)
  {
    _DELETE(*pos);
    m_theDcmStudies.erase(pos);
  }
  else
  {
    (*pos)->removeNoRefSeries();
  }

  return ERROR_SUCCESS;
}

CDcmStudy* CStudySetPool::findDcmStudy(const TCHAR* key) const
{
  if(key == NULL) return NULL;

  auto pos = find_if(m_theDcmStudies.begin(), m_theDcmStudies.end(),
    [&key](CDcmStudy* dcmStudy)
  {
    return _tcscmp(key, dcmStudy->getKey()) == 0;
  });

  if(pos != m_theDcmStudies.end())
    return *pos;

  return NULL;
}

DWORD CStudySetPool::addDcmStudy(CDcmStudy* dcmStudy)
{
  if(dcmStudy == NULL) return ERROR_INVALID_PARAMETER;

  try
  {
    m_theDcmStudies.push_back(dcmStudy);
  }
  catch(...)
  {
    return ERROR_NOT_ENOUGH_MEMORY;
  }

  return ERROR_SUCCESS;
}



